package cn.jsj.onclock.service.impl;

import cn.jsj.onclock.dao.*;
import cn.jsj.onclock.exception.CustomException;
import cn.jsj.onclock.management.ExcelWorkBookFactory;
import cn.jsj.onclock.pojo.*;
import cn.jsj.onclock.pojo.identity.GroupIdentityEnum;
import cn.jsj.onclock.pojo.identity.TaskStatusEnum;
import cn.jsj.onclock.pojo.identity.UserIdentityEnum;
import cn.jsj.onclock.service.SignService;
import cn.jsj.onclock.socket.WebSocketManager;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;

/**
 * @author 卢斌
 */
@Slf4j
@Service
public class SignServiceImpl implements SignService {

    private final SignDao signDao;

    private final UserDao userDao;

    private final LabelDao labelDao;

    private final ExcelWorkBookFactory excelWorkBookFactory;

    private final TaskDao taskDao;

    private final GroupDao groupDao;

    @Autowired
    public SignServiceImpl(SignDao signDao, TaskDao taskDao, GroupDao groupDao, LabelDao labelDao, UserDao userDao, ExcelWorkBookFactory excelWorkBookFactory) {
        this.signDao = signDao;
        this.taskDao = taskDao;
        this.groupDao = groupDao;
        this.labelDao = labelDao;
        this.userDao = userDao;
        this.excelWorkBookFactory = excelWorkBookFactory;
    }

    /**
     * 群相关负责人主动发起一次签到
     *
     * @param creatorId     签到发起者
     * @param aimGroupId    发起群组
     * @param startTime     签到开始时间
     * @param limitTime     签到截止时间
     * @param title         签到标题
     * @param labelId       绑定的签到标签Id
     * @param userIdList    签到用户Id列表
     * @return  签到信息实体Id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long addOneSign(Long creatorId, Long aimGroupId, Date startTime, Date limitTime, String title, Long labelId, List<Long> userIdList) {

        Date nowTime = new Date();
        if (startTime.before(nowTime)) {
            throw new CustomException("签到开始时间请选择将来某一时刻");
        }
        if (limitTime.before(nowTime)) {
            throw new CustomException("签到结束时间请选择将来某一时刻");
        }
        if (limitTime.before(startTime)) {
            throw new CustomException("签到截止时间请选择晚于签到开始时间的时刻");
        }

        UserGroupInfo userGroupInfo = groupDao.getUserJoinGroupInfo(aimGroupId, creatorId);
        if (userGroupInfo == null) {
            throw new CustomException("您不在该群组中，无法发起签到");
        }
        if (GroupIdentityEnum.hasPowerOfLaunchSign(userGroupInfo.getRole())) {

            LabelEnt labelEnt = labelDao.getLabelEntById(labelId);
            if (labelEnt == null) {
                throw new CustomException("找不到该签到标签信息");
            }

            Set<Long> groupMembers = new HashSet<>(groupDao.listAllUserIdInGroup(aimGroupId));
            for (Long one : userIdList) {
                if (! groupMembers.contains(one)) {
                    throw new CustomException("签到无法对群组外的用户生效，请您重新选择用户");
                }
            }
            SignEnt insertBody = new SignEnt();
            insertBody.setTitle(title);
            insertBody.setAimGroup(aimGroupId);
            insertBody.setLabelId(labelId);
            insertBody.setStartTime(startTime);
            insertBody.setLimitTime(limitTime);
            insertBody.setIssuer(creatorId);

            signDao.insertSignEnt(insertBody);

            taskDao.insertTask(insertBody.getId(), userIdList);
            // 查询需要完成签到的用户中，在线的用户

            for (Long one : userIdList) {
                WebSocketSession session = WebSocketManager.get(one.toString());
                if (session != null) {
                    try {
                        session.sendMessage(new TextMessage("signRequest:" + insertBody.getId()));
                    } catch (IOException e) {
                        log.info("================ socket推送失败，用户可能刚刚离线 ================");
                    }
                }
            }
            return insertBody.getId();
        } else {
            throw new CustomException("您暂无发起签到的权限");
        }
    }

    /**
     * 查询用户发起的签到列表（详细）
     *
     * @param userId   用户Id
     * @param pageNum  页码号
     * @param pageSize 页容量
     * @return 用户发起的签到列表
     */
    @Override
    public List<SignEnt> listSignEntOfIssuedFromUser(Long userId, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        return signDao.listSignEntAboutUser(userId);
    }

    /**
     * 查询用户相关的签到任务实体（详细）
     *
     * @param userId   用户Id
     * @param pageNum  页码号
     * @param pageSize 页容量
     * @return 用户相关的签到任务实体
     */
    @Override
    public List<TaskEnt> listExecutorTask(Long userId, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        return taskDao.listTaskEntAboutUser(userId);
    }

    /**
     * 用户查看自己发起的某一次签到的完成进度（用户仅仅可以看到自己发起的签到，管理员身份也可以查看别人的签到）
     *
     * @param userId        用户Id
     * @param userIdentity  用户身份
     * @param signId        用户要查看的签到Id
     * @return  某一签到的完成进度
     */
    @Override
    public List<TaskEnt> listSignFinishProgress(Long userId, UserIdentityEnum userIdentity, Long signId) {
        SignEnt signEnt = signDao.getSignEntById(signId);
        if (signEnt == null) {
            throw new CustomException("不存在该签到信息");
        }

        if (signEnt.getIssuer().equals(userId) || UserIdentityEnum.hasPowerOfCheckOthersSign(userIdentity)) {
            return taskDao.listSignFinishStatus(signId);
        } else {
            throw new CustomException("该签到不是您发起的，您无权查看");
        }
    }

    /**
     * 查询某一签到者在特定的几个签到中的完成情况
     *
     * @param userId        请求者
     * @param executorId    被调查者
     * @param signIdList    签到ID列表
     * @param userIdentity  用户身份权限
     * @return  某一签到者在特定的几个签到中的完成情况
     */
    @Override
    public List<TaskEnt> listExecutorFinishProgressInSpecialSign(Long userId, Long executorId, List<Long> signIdList, UserIdentityEnum userIdentity) {
        // signIdList去重
        HashSet<Long> distinctUtil = new HashSet<>(signIdList);
        signIdList = new ArrayList<>(distinctUtil);
        // 如果用户没有查看其他用户发起的签到的权限则需要验证这些签到是否是请求者发起的
        if (! UserIdentityEnum.hasPowerOfCheckOthersSign(userIdentity)) {

            List<Long> userIdList = signDao.listSenderOfSign(signIdList);
            if (CollectionUtils.isEmpty(userIdList) || userIdList.size() > 1) {
                throw new CustomException("无效的签到列表");
            }
            Long getUserId = userIdList.get(0);
            if (!userId.equals(getUserId)) {
                throw new CustomException("您无权查看这些人的签到信息");
            }
        }
        return taskDao.listSpecialTaskOfExecutor(executorId, signIdList);
    }

    /**
     * 查询用户在某一群组中的发起签到记录
     *
     * @param userId    用户Id
     * @param groupId   查询的群组Id
     * @param from      开始时间
     * @param until     截止时间（默认是现在）
     * @param pageNum   页码号
     * @param pageSize  页容量
     * @return  查询发起的签到记录
     */
    @Override
    public List<SignEnt> listSignRecord(Long userId, Long groupId, Date from, Date until, Integer pageNum, Integer pageSize) {
        Date now = new Date();
        if (from.after(now)) {
            throw new CustomException("开始时间请选择过去时间");
        }
        if (until == null) {
            until = now;
        }
        PageHelper.startPage(pageNum, pageSize);
        return signDao.listSignInfoInGroupFromTime(userId, groupId, from, until);
    }

    /**
     * 群组内部分人员的签到信息Excel表格导出
     *
     * @param userId            用户Id
     * @param userIdentityEnum  用户身份
     * @param groupId           群组Id
     * @param executorIdList    签到者Id列表
     * @param signIdList        签到Id列表
     * @param response          HttpServletResponse
     */
    @Override
    public void clockRecordOutPutExcel(Long userId, UserIdentityEnum userIdentityEnum, Long groupId, List<Long> executorIdList, List<Long> signIdList, HttpServletResponse response) {
        executorIdList = this.distinctLongList(executorIdList);
        signIdList = this.distinctLongList(signIdList);
        List<SignEnt> signEntList;
        if (! UserIdentityEnum.ADMINISTRATOR.equals(userIdentityEnum)) {
            UserGroupInfo userGroupInfo = groupDao.getUserJoinGroupInfo(groupId, userId);
            if (userGroupInfo == null) {
                throw new CustomException("您无权导出该信息");
            }
            signEntList = signDao.listSignInfoInIdListFromSender(signIdList, groupId, userId);
        } else {
            signEntList = signDao.listSignInfoInIdList(signIdList, groupId);
        }
        if (signEntList.size() != signIdList.size()) {
            throw new CustomException("您选择了错误的签到信息");
        }
        List<UserEnt> userEntList = userDao.listUserInfoInIdList(executorIdList);
        XSSFWorkbook excel = excelWorkBookFactory.getWorkbook();
        XSSFSheet sheet = excel.getSheetAt(0);
        XSSFRow firstRow = sheet.getRow(0);
        // 初始化一个从Excel列编号到签到信息Id的映射容器，初始大小为内容数量除以0.75 然后加一
        HashMap<Long, Integer> signIdToCellIndexMap = new HashMap<>((signIdList.size() * 4 / 3) + 1);
        HashMap<Long, Date> signIdToLimitTimeMap = new HashMap<>((signIdList.size() * 4 / 3) + 1);
        int cellNumIndex = 1;
        XSSFCellStyle simpleCellStyle = this.createDumpSignExcelCellStyle(excel, null);
        for (SignEnt oneSign : signEntList) {
            firstRow.createCell(cellNumIndex).setCellStyle(simpleCellStyle);
            firstRow.getCell(cellNumIndex).setCellValue(oneSign.getTitle());
            signIdToCellIndexMap.put(oneSign.getId(), cellNumIndex);
            signIdToLimitTimeMap.put(oneSign.getId(), oneSign.getLimitTime());
            cellNumIndex = cellNumIndex + 1;
        }
        XSSFCellStyle finishedCellStyle = this.createDumpSignExcelCellStyle(excel, IndexedColors.LIGHT_GREEN);
        XSSFCellStyle unFinishedCellStyle = this.createDumpSignExcelCellStyle(excel, IndexedColors.CORAL);
        XSSFCellStyle waitingCellStyle = this.createDumpSignExcelCellStyle(excel, IndexedColors.LIGHT_YELLOW);
        Date nowTime = new Date();
        int rowNumIndex = 1;
        for (UserEnt oneUser : userEntList) {
            XSSFRow userRow = sheet.createRow(rowNumIndex);
            userRow.setHeight((short) 530);
            userRow.createCell(0).setCellStyle(simpleCellStyle);
            userRow.getCell(0).setCellValue(oneUser.getName());
            List<TaskEnt> task = taskDao.listSpecialTaskOfExecutor(oneUser.getId(), signIdList);
            for (TaskEnt oneTask : task) {
                Integer cellId = signIdToCellIndexMap.get(oneTask.getSignId());
                if (TaskStatusEnum.FINISHED.equals(oneTask.getStatusEnum())) {
                    userRow.createCell(cellId).setCellStyle(finishedCellStyle);
                    userRow.getCell(cellId).setCellValue("已完成");
                    continue;
                }
                Date signLimitTime = signIdToLimitTimeMap.get(oneTask.getSignId());
                if (nowTime.after(signLimitTime)) {
                    userRow.createCell(cellId).setCellStyle(unFinishedCellStyle);
                    userRow.getCell(cellId).setCellValue("未完成");
                } else {
                    userRow.createCell(cellId).setCellStyle(waitingCellStyle);
                    userRow.getCell(cellId).setCellValue("待完成");
                }
            }
        }
        this.pushExcelToClient(excel, response);
    }

    private XSSFCellStyle createDumpSignExcelCellStyle(XSSFWorkbook excel, IndexedColors color) {
        XSSFCellStyle xssfCellStyle = excel.createCellStyle();
        xssfCellStyle.setAlignment(HorizontalAlignment.CENTER);
        xssfCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        if (color != null) {
            xssfCellStyle.setFillForegroundColor(color.getIndex());
            xssfCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        }
        return xssfCellStyle;
    }


    /**
     * Long类型的List去重
     */
    private ArrayList<Long> distinctLongList(List<Long> longList) {
        ArrayList<Long> arrayList = new ArrayList<>();
        if (! CollectionUtils.isEmpty(longList)) {
            HashSet<Long> hashSet = new HashSet<>(longList);
            arrayList = new ArrayList<Long>(hashSet);
        }
        return arrayList;
    }

    /**
     * 推送Excel文件到客户端
     */
    private void pushExcelToClient(XSSFWorkbook excel, HttpServletResponse response) {
        OutputStream outputStream = null;
        try {
            response.setHeader("Content-Disposition","attachment;filename=record.xlsx");
            outputStream = response.getOutputStream();
            excel.write(outputStream);
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException("有错误");
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


}
